package com.itaming.lycheeframework.support.utils;

import com.itaming.lycheeframework.common.constant.FrameworkConstant;
import lombok.experimental.UtilityClass;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalUnit;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
 * DateTime工具类
 *
 * @author A.Ming
 */
@UtilityClass
public class DateTimeUtil {

    /**
     * DateTimeFormatter
     */
    public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(FrameworkConstant.DATETIME_FORMAT);
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(FrameworkConstant.DATE_FORMAT);
    public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(FrameworkConstant.TIME_FORMAT);

    /**
     * 转为日期时间
     *
     * @param date Date
     * @return 日期时间
     */
    public static LocalDateTime of(Date date) {
        return date == null ? null : of(date.toInstant());
    }

    /**
     * 转为日期时间
     *
     * @param instant Instant
     * @return 日期时间
     */
    public static LocalDateTime of(Instant instant) {
        return of(instant, ZoneId.systemDefault());
    }

    /**
     * 转为UTC日期时间
     *
     * @param instant Instant
     * @return 日期时间
     */
    public static LocalDateTime ofUTC(Instant instant) {
        return of(instant, ZoneId.of("UTC"));
    }

    /**
     * 转为日期时间
     *
     * @param zonedDateTime ZonedDateTime
     * @return 日期时间
     */
    public static LocalDateTime of(ZonedDateTime zonedDateTime) {
        return null == zonedDateTime ? null : zonedDateTime.toLocalDateTime();
    }

    /**
     * 转为日期时间
     *
     * @param instant  Instant
     * @param timeZone 时区
     * @return 日期时间
     */
    public static LocalDateTime of(Instant instant, TimeZone timeZone) {
        return of(instant, TimeZoneUtil.defaultIfNull(timeZone).toZoneId());
    }

    /**
     * 转为日期时间
     *
     * @param instant Instant
     * @param zoneId  时区
     * @return 日期时间
     */
    public static LocalDateTime of(Instant instant, ZoneId zoneId) {
        return null == instant ? null : LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId::systemDefault));
    }

    /**
     * 转为日期时间
     *
     * @param epochMilli 纪元毫秒
     * @return 日期时间
     */
    public static LocalDateTime of(long epochMilli) {
        return of(Instant.ofEpochMilli(epochMilli));
    }

    /**
     * 转为UTC日期时间
     *
     * @param epochMilli 纪元毫秒
     * @return 日期时间
     */
    public static LocalDateTime ofUTC(long epochMilli) {
        return ofUTC(Instant.ofEpochMilli(epochMilli));
    }

    /**
     * 转为日期时间
     *
     * @param epochMilli 纪元毫秒
     * @param zoneId     时区
     * @return 日期时间
     */
    public static LocalDateTime of(long epochMilli, ZoneId zoneId) {
        return of(Instant.ofEpochMilli(epochMilli), zoneId);
    }

    /**
     * 转为日期时间
     *
     * @param epochMilli 纪元毫秒
     * @param timeZone   时区
     * @return 日期时间
     */
    public static LocalDateTime of(long epochMilli, TimeZone timeZone) {
        return of(Instant.ofEpochMilli(epochMilli), timeZone);
    }

    /**
     * 转为日期时间
     *
     * @param calendar Calendar
     * @return 日期时间
     */
    public static LocalDateTime of(Calendar calendar) {
        return calendar == null ? null : of(calendar.toInstant(), calendar.getTimeZone());
    }

    /**
     * 格式化为日期时间字符串
     *
     * @param temporal 日期时间
     * @return 格式化后的字符串
     */
    public static String formatDateTime(TemporalAccessor temporal) {
        return format(temporal, DATETIME_FORMATTER);
    }

    /**
     * 格式化为日期字符串
     *
     * @param temporal 日期时间
     * @return 格式化后的字符串
     */
    public static String formatDate(TemporalAccessor temporal) {
        return format(temporal, DATE_FORMATTER);
    }

    /**
     * 格式化为时间字符串
     *
     * @param temporal 日期时间
     * @return 格式化后的字符串
     */
    public static String formatTime(TemporalAccessor temporal) {
        return format(temporal, TIME_FORMATTER);
    }

    /**
     * 按照指定的格式格式化日期时间
     *
     * @param temporal 日期时间
     * @param format   格式
     * @return 格式化后的字符串
     */
    public static String format(TemporalAccessor temporal, String format) {
        return format(temporal, DateTimeFormatter.ofPattern(format));
    }

    /**
     * 使用指定的<code>formatter</code>格式化日期时间
     *
     * @param temporal  日期时间
     * @param formatter 格式化器
     * @return 格式化后的字符串
     */
    public static String format(TemporalAccessor temporal, DateTimeFormatter formatter) {
        return formatter == null ? null : formatter.format(temporal);
    }

    /**
     * 解析日期时间
     *
     * @param dateStr 字符串
     * @return 日期时间
     */
    public static LocalDateTime parse(String dateStr) {
        return parse(dateStr, DATETIME_FORMATTER);
    }

    /**
     * 根据指定的格式解析日期时间
     *
     * @param dateStr 字符串
     * @param format  格式
     * @return 日期时间
     */
    public static LocalDateTime parse(String dateStr, String format) {
        return parse(dateStr, DateTimeFormatter.ofPattern(format));
    }

    /**
     * 使用指定的格式化器解析日期时间
     *
     * @param dateStr   字符串
     * @param formatter 格式化器
     * @return 日期时间
     */
    public static LocalDateTime parse(String dateStr, DateTimeFormatter formatter) {
        return dateStr == null ? null : LocalDateTime.parse(dateStr, formatter);
    }

    /**
     * 解析日期
     *
     * @param dateStr 字符串
     * @return 日期
     */
    public static LocalDate parseLocalDate(String dateStr) {
        return parseLocalDate(dateStr, DATE_FORMATTER);
    }

    /**
     * 根据指定的格式解析日期
     *
     * @param dateStr 字符串
     * @param format  格式
     * @return 日期
     */
    public static LocalDate parseLocalDate(String dateStr, String format) {
        return parseLocalDate(dateStr, DateTimeFormatter.ofPattern(format));
    }

    /**
     * 使用指定的格式化器解析日期
     *
     * @param dateStr   字符串
     * @param formatter 格式化器
     * @return 日期
     */
    public static LocalDate parseLocalDate(String dateStr, DateTimeFormatter formatter) {
        return dateStr == null ? null : LocalDate.parse(dateStr, formatter);
    }

    /**
     * 解析时间
     *
     * @param dateStr 字符串
     * @return 时间
     */
    public static LocalTime parseLocalTime(String dateStr) {
        return parseLocalTime(dateStr, TIME_FORMATTER);
    }

    /**
     * 根据指定的格式解析时间
     *
     * @param dateStr 字符串
     * @param format  格式
     * @return 时间
     */
    public static LocalTime parseLocalTime(String dateStr, String format) {
        return parseLocalTime(dateStr, DateTimeFormatter.ofPattern(format));
    }

    /**
     * 使用指定的格式化器解析时间
     *
     * @param dateStr   字符串
     * @param formatter 格式化器
     * @return 时间
     */
    public static LocalTime parseLocalTime(String dateStr, DateTimeFormatter formatter) {
        return dateStr == null ? null : LocalTime.parse(dateStr, formatter);
    }

    /**
     * 转为日历
     *
     * @param localDateTime 日期时间
     * @return 日历
     */
    public static Calendar toCalendar(LocalDateTime localDateTime) {
        return GregorianCalendar.from(ZonedDateTime.of(localDateTime, ZoneId.systemDefault()));
    }

    /**
     * 日期时间转为纪元秒
     *
     * @param localDateTime 日期时间
     * @return 纪元秒
     */
    public static long toEpochSecond(TemporalAccessor localDateTime) {
        return toEpochMilli(localDateTime) / 1000;
    }

    /**
     * 日期时间转为纪元毫秒
     *
     * @param temporal 日期时间
     * @return 纪元毫秒
     */
    public static long toEpochMilli(TemporalAccessor temporal) {
        return temporal == null ? 0 : toInstant(temporal).toEpochMilli();
    }

    /**
     * 日期时间转为<code>Instant</code>
     *
     * @param temporal 日期时间
     * @return Instant
     */
    public static Instant toInstant(TemporalAccessor temporal) {
        return toInstant(temporal, ZoneId.systemDefault());
    }

    /**
     * 日期时间转为<code>Instant</code>
     *
     * @param temporalAccessor 日期时间
     * @param zoneId           时区
     * @return Instant
     */
    public static Instant toInstant(TemporalAccessor temporalAccessor, ZoneId zoneId) {
        if (null == temporalAccessor) {
            return null;
        }

        zoneId = ObjectUtil.defaultIfNull(zoneId, ZoneId::systemDefault);

        Instant instant;
        if (temporalAccessor instanceof Instant) {
            instant = (Instant) temporalAccessor;
        } else if (temporalAccessor instanceof LocalDateTime) {
            instant = ((LocalDateTime) temporalAccessor).atZone(zoneId).toInstant();
        } else if (temporalAccessor instanceof ZonedDateTime) {
            instant = ((ZonedDateTime) temporalAccessor).toInstant();
        } else if (temporalAccessor instanceof OffsetDateTime) {
            instant = ((OffsetDateTime) temporalAccessor).toInstant();
        } else if (temporalAccessor instanceof LocalDate) {
            instant = ((LocalDate) temporalAccessor).atStartOfDay(zoneId).toInstant();
        } else if (temporalAccessor instanceof LocalTime) {
            instant = ((LocalTime) temporalAccessor).atDate(LocalDate.now()).atZone(zoneId).toInstant();
        } else if (temporalAccessor instanceof OffsetTime) {
            instant = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant();
        } else {
            instant = Instant.from(temporalAccessor);
        }

        return instant;
    }

    /**
     * 获取日期时间中的日期部分
     *
     * @param localDateTime 日期时间
     * @return 日期
     */
    public static LocalDate toLocalDate(LocalDateTime localDateTime) {
        return localDateTime == null ? null : localDateTime.toLocalDate();
    }

    /**
     * 获取日期时间中的日期部分
     *
     * @param localDateTime 日期时间
     * @return 时间
     */
    public static LocalTime toLocalTime(LocalDateTime localDateTime) {
        return localDateTime == null ? null : localDateTime.toLocalTime();
    }

    /**
     * 秒数转为日时分秒字符串
     *
     * @param seconds 秒数
     * @return 字符串
     */
    public static String secondToTime(Integer seconds) {
        // 判断是否为空
        if (seconds == null || seconds <= 0L) {
            return StringPool.EMPTY;
        }

        long second = seconds;

        // 转换天数
        long days = second / 86400;
        // 剩余秒数
        second = second % 86400;

        // 转换小时
        long hours = second / 3600;
        // 剩余秒数
        second = second % 3600;

        // 转换分钟
        long minutes = second / 60;
        // 剩余秒数
        second = second % 60;

        if (days == 0) {
            return String.format("%s小时%s分%s秒", hours, minutes, second);
        }

        return String.format("%s天%s小时%s分%s秒", days, hours, minutes, second);
    }

    /**
     * 替换时间为<code>00:00:00.0</code>
     *
     * @param localDateTime 日期时间
     * @return 日期时间
     */
    public static LocalDateTime withStartOfDay(LocalDateTime localDateTime) {
        return withTime(localDateTime, LocalTime.MIN);
    }

    /**
     * 替换时间为<code>23:59:59.999999999</code>
     *
     * @param localDateTime 日期时间
     * @return 日期时间
     */
    public static LocalDateTime withEndOfDay(LocalDateTime localDateTime) {
        return withTime(localDateTime, LocalTime.MAX);
    }

    /**
     * 替换时间
     *
     * @param localDateTime 日期时间
     * @param localTime     要修改的时间
     * @return 日期时间
     */
    public static LocalDateTime withTime(LocalDateTime localDateTime, LocalTime localTime) {
        if (localDateTime == null) {
            return null;
        }

        if (localTime == null || localDateTime.toLocalTime().equals(localTime)) {
            return localDateTime;
        }

        return localDateTime.with(localTime);
    }

    /**
     * 日期转为日期时间，指定时间为<code>00:00:00.0</code>
     *
     * @param localDate 日期时间
     * @return 日期时间
     */
    public static LocalDateTime atStartOfDay(LocalDate localDate) {
        return atTime(localDate, LocalTime.MIN);
    }

    /**
     * 日期转为日期时间，指定时间为<code>23:59:59.999999999</code>
     *
     * @param localDate 日期时间
     * @return 日期时间
     */
    public static LocalDateTime atEndOfDay(LocalDate localDate) {
        return atTime(localDate, LocalTime.MAX);
    }

    /**
     * 使用指定的时间将日期转为日期时间
     *
     * @param localDate 日期
     * @param localTime 要修改的时间
     * @return 日期时间
     */
    public static LocalDateTime atTime(LocalDate localDate, LocalTime localTime) {
        return localDate == null ? null : localDate.atTime(localTime);
    }

    /**
     * 加年
     *
     * @param temporal 日期时间
     * @param years    年数
     * @return Temporal
     */
    public static <T extends Temporal> T plusYears(T temporal, long years) {
        return plus(temporal, years, ChronoUnit.YEARS);
    }

    /**
     * 加月
     *
     * @param temporal 日期时间
     * @param months   月数
     * @return Temporal
     */
    public static <T extends Temporal> T plusMonths(T temporal, long months) {
        return plus(temporal, months, ChronoUnit.MONTHS);
    }

    /**
     * 加天
     *
     * @param temporal 日期时间
     * @param days     天数
     * @return Temporal
     */
    public static <T extends Temporal> T plusDays(T temporal, long days) {
        return plus(temporal, days, ChronoUnit.DAYS);
    }

    /**
     * 加小时
     *
     * @param temporal 日期时间
     * @param hours    小时数
     * @return Temporal
     */
    public static <T extends Temporal> T plusHours(T temporal, long hours) {
        return plus(temporal, hours, ChronoUnit.HOURS);
    }

    /**
     * 加分钟
     *
     * @param temporal 日期时间
     * @param minutes  分钟数
     * @return Temporal
     */
    public static <T extends Temporal> T plusMinutes(T temporal, long minutes) {
        return plus(temporal, minutes, ChronoUnit.MINUTES);
    }

    /**
     * 加秒
     *
     * @param temporal 日期时间
     * @param seconds  秒数
     * @return Temporal
     */
    public static <T extends Temporal> T plusSeconds(T temporal, long seconds) {
        return plus(temporal, seconds, ChronoUnit.SECONDS);
    }

    /**
     * 加毫秒
     *
     * @param temporal 日期时间
     * @param millis   毫秒数
     * @return Temporal
     */
    public static <T extends Temporal> T plusMillis(T temporal, long millis) {
        return plus(temporal, millis, ChronoUnit.MILLIS);
    }

    /**
     * 加微秒
     *
     * @param temporal 日期时间
     * @param micros   微秒数
     * @return Temporal
     */
    public static <T extends Temporal> T plusMicros(T temporal, long micros) {
        return plus(temporal, micros, ChronoUnit.MICROS);
    }

    /**
     * 加纳秒
     *
     * @param temporal 日期时间
     * @param nanos    纳秒数
     * @return Temporal
     */
    public static <T extends Temporal> T plusNanos(T temporal, long nanos) {
        return plus(temporal, nanos, ChronoUnit.NANOS);
    }

    /**
     * 加时间
     *
     * @param temporal 日期时间
     * @param amount   数量
     * @param unit     单位
     * @return
     */
    @SuppressWarnings("unchecked")
    private static <T extends Temporal> T plus(T temporal, long amount, TemporalUnit unit) {
        return temporal == null ? null : (T) temporal.plus(amount, unit);
    }

    /**
     * 减年
     *
     * @param temporal 日期时间
     * @param years    年数
     * @return Temporal
     */
    public static <T extends Temporal> T minusYears(T temporal, long years) {
        return minus(temporal, years, ChronoUnit.YEARS);
    }

    /**
     * 减月
     *
     * @param temporal 日期时间
     * @param months   月数
     * @return Temporal
     */
    public static <T extends Temporal> T minusMonths(T temporal, long months) {
        return minus(temporal, months, ChronoUnit.MONTHS);
    }

    /**
     * 减天
     *
     * @param temporal 日期时间
     * @param days     天数
     * @return Temporal
     */
    public static <T extends Temporal> T minusDays(T temporal, long days) {
        return minus(temporal, days, ChronoUnit.DAYS);
    }

    /**
     * 减小时
     *
     * @param temporal 日期时间
     * @param hours    小时数
     * @return Temporal
     */
    public static <T extends Temporal> T minusHours(T temporal, long hours) {
        return minus(temporal, hours, ChronoUnit.HOURS);
    }

    /**
     * 减分钟
     *
     * @param temporal 日期时间
     * @param minutes  分钟数
     * @return Temporal
     */
    public static <T extends Temporal> T minusMinutes(T temporal, long minutes) {
        return minus(temporal, minutes, ChronoUnit.MINUTES);
    }

    /**
     * 减秒
     *
     * @param temporal 日期时间
     * @param seconds  秒数
     * @return Temporal
     */
    public static <T extends Temporal> T minusSeconds(T temporal, long seconds) {
        return minus(temporal, seconds, ChronoUnit.SECONDS);
    }

    /**
     * 减毫秒
     *
     * @param temporal 日期时间
     * @param millis   毫秒数
     * @return Temporal
     */
    public static <T extends Temporal> T minusMillis(T temporal, long millis) {
        return minus(temporal, millis, ChronoUnit.MILLIS);
    }

    /**
     * 减微秒
     *
     * @param temporal 日期时间
     * @param micros   微秒数
     * @return Temporal
     */
    public static <T extends Temporal> T minusMicros(T temporal, long micros) {
        return minus(temporal, micros, ChronoUnit.MICROS);
    }

    /**
     * 减纳秒
     *
     * @param temporal 日期时间
     * @param nanos    纳秒数
     * @return Temporal
     */
    public static <T extends Temporal> T minusNanos(T temporal, long nanos) {
        return minus(temporal, nanos, ChronoUnit.NANOS);
    }

    /**
     * 减时间
     *
     * @param temporal 日期时间
     * @param amount   数量
     * @param unit     单位
     * @return
     */
    @SuppressWarnings("unchecked")
    private static <T extends Temporal> T minus(T temporal, long amount, TemporalUnit unit) {
        return temporal == null ? null : (T) temporal.minus(amount, unit);
    }

    /**
     * 是否为周末
     *
     * @param localDateTime 日期时间
     * @return boolean
     */
    public static boolean isWeekend(LocalDateTime localDateTime) {
        return isWeekend(localDateTime.toLocalDate());
    }

    /**
     * 是否为周末
     *
     * @param localDate 日期
     * @return boolean
     */
    public static boolean isWeekend(LocalDate localDate) {
        DayOfWeek dayOfWeek = localDate.getDayOfWeek();
        return DayOfWeek.SATURDAY == dayOfWeek || DayOfWeek.SUNDAY == dayOfWeek;
    }

    /**
     * 是否为同一天
     *
     * @param date1 日期时间1
     * @param date2 日期时间2
     * @return boolean
     */
    public static boolean isSameDay(LocalDateTime date1, LocalDateTime date2) {
        return date1 != null && date2 != null && isSameDay(date1.toLocalDate(), date2.toLocalDate());
    }

    /**
     * 是否为同一天
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return boolean
     */
    public static boolean isSameDay(LocalDate date1, LocalDate date2) {
        return date1 != null && date2 != null && date1.isEqual(date2);
    }

    /**
     * 是否为同一时间
     *
     * @param date1 日期时间1
     * @param date2 日期时间2
     * @return boolean
     */
    public static boolean isSameTime(LocalDateTime date1, LocalDateTime date2) {
        return date1 != null && date2 != null && isSameTime(date1.toLocalTime(), date2.toLocalTime());
    }

    /**
     * 两是否为同一时间
     *
     * @param time1 时间1
     * @param time2 时间2
     * @return boolean
     */
    public static boolean isSameTime(LocalTime time1, LocalTime time2) {
        return time1 != null && time1.equals(time2);
    }

    /**
     * 日期时间是否在区间内
     *
     * @param date      日期时间
     * @param beginDate 开始日期时间
     * @param endDate   结束日期时间
     * @return boolean
     */
    public static boolean isIn(TemporalAccessor date, TemporalAccessor beginDate, TemporalAccessor endDate) {
        return isIn(date, beginDate, endDate, true, true);
    }

    /**
     * 日期时间是否在区间内
     *
     * @param date         日期时间
     * @param beginDate    开始日期时间
     * @param endDate      结束日期时间
     * @param includeBegin 是否包含开始
     * @param includeEnd   是否包含结束
     * @return boolean
     */
    public static boolean isIn(TemporalAccessor date, TemporalAccessor beginDate, TemporalAccessor endDate, boolean includeBegin, boolean includeEnd) {
        if (date == null || beginDate == null || endDate == null) {
            return false;
        }

        long thisMills = toEpochMilli(date);
        long beginMills = toEpochMilli(beginDate);
        long endMills = toEpochMilli(endDate);
        long rangeMin = Math.min(beginMills, endMills);
        long rangeMax = Math.max(beginMills, endMills);
        boolean isIn = rangeMin < thisMills && thisMills < rangeMax;
        if (!isIn && includeBegin) {
            isIn = thisMills == rangeMin;
        }

        if (!isIn && includeEnd) {
            isIn = thisMills == rangeMax;
        }

        return isIn;
    }

}
